Table of Contents

Item Tracker Project

This is a proof-of-concept system designed to catalogue, track, and recover a user's items. Check out our Youtube Video too!

Introduction

The idea for the Never Lost project originated because both of us carry around reusable water bottles that we sometimes misplace. We wanted to create a device that would keep track of whether our water bottle, or any other item, was present and if not, give the the location of where it was left. After running through multiple ideas, we decided to create a system that would utilize RFID to tell if our item was present and GPS to record our location, which could be displayed to us at a later point if our item was missing.

The purpose of the RFID module is to periodically check if your item is present. By attaching the RFID tag to your item and placing it near the module, it can scan the tag. A successful read means the item is there and an unsuccessful means the item is likely missing. In order to make our code efficient, the RFID tag is read periodically in an interrupt, setup by the python Signals library. This library allows us to specify which function is the interrupt handler as well as the interval between interrupts.

Since the RFID module has a short range, we have implemented some features to improve our design. First, in each interrupt call we attempt to read the RFID tag multiple times. This helps prevent the case where our item is present, but a single bad read results in the item "missing". We then added a query to the user if we cannot detect their item. Instead of just automatically showing them the last GPS coordinates of the item, we first ask them if they are missing their item. For example, a user who removes the item from their backpack to hold can now decline the option to search for their object.

Our last improvement is that regardless of the state of the program, (Initial welome state, query state or finding state), the RFID interrupt always occur. If the RFID tag is read at any point, we transition to the 'Initial Welcome State', which indicates that your item is present. This was a crucial addition as it allows for the program to self correct a bad RFID read in a following period and minimizes the user's interaction with the touch-screen. They can simply replace the item near the RFID module and resume as normal.

The next part of our system is the GPS, where we used the Adafruit ULtimate GPS Breakout V3. We read GPS data at regular intervals, appending them to a list which essentially gives us the path that the user has been traveling. Whenever the RFID tag is successfully read, clear our list of coordinates and start over. This has dual functions of helping to keep our list to a resonable size and only saving the of coordinates to the last location where we knew the item was present. For example, if we travled from points A -> B -> C and we know our item was present at point B, there is not need to save the location of point A.

If the RFID tag is not read sucessfully, we ask the user on the PiTFT screen if their item is missing. They can respond with Yes or No. If they choose "No", we return to the main screen. If they choose "Yes", we then display the last coordinates where there item was detected by the RFID. These GPS coordinates can then be typed into Google Maps, giving you the location of your item.

The GPS is quite accurate. From initial testing, our GPS was within 30 feet of our actual location. However, it is less reliable during snow storms.

GPS accuracy (within 30 ft)

Objectives

Design

We aimed to use minimal hardware with the goal of a portable, always-on, reliable and long-lived system. All power is sourced from the Pi, and with only the GPS and RFID module as extra components, there is not much to break on a hike or out in the city. Correspondingly, the code base is simple but easily extensible, following respected and useful design archetypes.

Dependencies and References

Python 3.7.3

CircuitPython Instructions here

GPS library
pip3 install adafruit-circuitpython-gps

RFID PN532-breakout board library
sudo pip3 install adafruit-circuitpython-pn532

GPS demo https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/uart-serial The Pi 4 doesn't support UART, so the uart object is actually a Serial object using the "/dev/ttyS0" (tty S zero) pipe.

GPS Packet Datasheet: https://cdn-shop.adafruit.com/datasheets/PMTK_A11.pdf

UI

The basic states of the project can be summed up in a FSM.

Initial State: Welcome Menu with items

State transition to Question State on one missed period with a tag.

Question State:

State transition to Initial State on 'No'.

State transition to Finding State on 'Yes'.

Finding State:

State transition to Initial State on new valid read or 'Found'.

GPS:

The actions we take with respect to the GPS

Global Action:

Initial State:

Question State:

Finding State:

RFID

General RFID Information and Selection

We chose to use RFID over bluetooth, because the RFID tag is passive, requiring no external power. Using bluetooth, we would have to power both the bluetooth breakout module and the transmitter. We decided to use an RFID module that operated at high frequency, which is standarized at 13.56 MHz. Using higher frequencies allows you to read RFID tags further away, but diminishes the ability to read through materials. Since the item we are tracking will likely be placed in a backpack, we want to ensure we can read the tag through thin material.
Professor Skovira had the PN532_breakout board which is compatible for RFID, as well as RFID tags, so after reviewing the documentation we decided to use these. The only downside is that high frequency RFID has two different standards. The more common and easy to use standard is the ISO 14443 standard, which is what the PN532 uses. However, this RFID standard has a maximum reading radius of 10 cm, or about 4 inches. This means that our tag will have to be quite close to the PN532 module, but it is much easier to use than an RFID module using the the other standard, ISO 15693.

RFID PN532 Module: Documentation and Setup

In addition, you must enable the first SPI channel on the raspberry-pi, as the SPI0 is used by the PiTFT. This can be done by editing the /boot/config.txt file and adding
dtoverlay=spi1-1cs

Pin Configuration

Function RPi Broadcom Pin PN532 Pin
MOSI 20 MOSI
MISO 19 MISO
SCLK 21 SCK
Digital 26 SSEL

More docmentation can be found on the Adafruit Website

For specifics on the RFID module, look at the PN532 Datasheet

Global Action:

No states change this action, though several state transitions occur depending on the result of the action.

Hardware

Our harware was not overly complicated. We soldered headers to the GPS module so we could plug it directly into the breadboard and used jumpers to connect the RFID module to the breadboard. Our Pi was also connected to the breadboard via the breakout cables.

For real-world application of our project, you would place the breadboard and Pi outside of your backpack for the best results. This allows you to see and interact with the PiTFT screen and helps the GPS module keep a lock on the satellites. For example, we placed the Pi and breadbaord in the side pocket of our backpack.

Breadboard and Pi

Breadboard and Pi closer

You can then place the item you are tracking in your bag with the RFID module. Keeping them close together is important so the RFID module can read the tag. Here, you can see a small circular RFID tag taped to my water bottle, near the RFID module. It is secured inside the bag by other notebooks and binders.

RFID module and item

User Input

See UI 'Buttons'.

Program Layout

RFID Layout

The RFID module is encapsulated in an RFID class that contains:

GPS Layout

We are using the Adafruit GPS class, documentation can be found here

UI Layout

We have custom-written classes for Buttons (general displayable text) and Images, mostly written for previous labs.

The Image class has been expanded upon to enable rotatioon.

We designed the user interface to be simple to engage with at a glance, by keeping all information front and center and buttons to the bottom. The FSM design

Testing

We tested each component with minimal software, independently of each other and the main UI to simplify the debug process. The assembly of the devices is not complicated, and did not warrant extensive stressing. The GPS module was similarly simple to engage with, by using a currently maintained and easy to use library provided by Adafruit.

Testing the RFID module was slightly more complicated. Enabling and using a second SPI channel was necessary since the PiTFT uses SPI0. After the setup, we then looked for the best way to create a timer interrupt, so we could read the RFID tag periodically without polling. This makes our code more efficient and elegant. We decided to use the Signals library in Python, which allowed us to set a timer for our interrupt and define our interrupt handler.

Once this was copmlete we tested the RFID by attempting to read the tag's UID. We did this with several different tags and found the maximum range was about 4 inches for all the tags. We also tested to see if the tags could be read with other objects in the way and this was mostly successful. In general the tags could be read through any thin object that kept the tag witin 4 inches of the RFID module.

Testing the entire system was relatively simple, once we were confident in the RFID and GPS subsystem performance. We discovered some bugs in our UI code by exhaustively testing every input combination, though that methodology is only feasible because we were careful to keep the interface minimal. In our testing, we also discovered that the GPS library returns non-numeric values if it does not have a lock on satellites, and ended up adding an extra state to the application to account for it.

Further Work

Currently the power draw for the entire system is at the edge of what the Raspberry Pi can reliably source, much less a smaller chip. Due to the simplicity of the code, it is reasonable to cram the system into a small chip to be placed in a bag or pouch, and a partner app. While using a GPS in the system is interesting, nearly everybody carries a more accurate and powerful device with them at all times, and leveraging it in tandem with specialized hardware geared towards tagging and tracking items is the logical next step for development.

Contributors

Eric Kahn (edk52) RFID software, testing, and project hardware.
Eric Hall (ewh73) GPS software, testing, and UI

Budget

Item Price
RFID Module + Tags $39.95
GPS Module $39.95
Total $79.90

Acknowledgments

Prof. Skovira For teaching ECE 5725 and providing the RFID module, and advice on the detection system

Software

Visit our Github for the source! To run at startup, add sudo /path/to/main.py to rc.local.

(main.py)[https://github.coecis.cornell.edu/edk52/5725_FinalProject/blob/master/main.py]:

import RPi.GPIO as gpio
import time
import os
import pygame as pg
from pygame.locals import *
from UI.Button import Button
from UI.Image import Image
import serial
import adafruit_gps
from RFID.RFID import RFID

time.sleep(1)
print('Running main.py!!!!')

# =========-Functions-==========
from serial.tools.list_ports import comports

# =========-Functions-==========
found_serial = False
while not found_serial:
    for port in comports():
        if port.name == 'ttyAMA0':
            found_serial = True
    time.sleep(0.2)

# =========-Constants-========
timeout = 120
BLACK = 0,0,0
DARKGREY = 20,20,20
WHITE = 255,255,255
size = w,h = 320,240

qpin = 27
framerate = 15.0
starttime = time.time()

# =========-GPIO-==========
gpio.setmode(gpio.BCM)
gpio.setup(qpin, gpio.IN, pull_up_down=gpio.PUD_UP)

# =========-SYSTEM VARS-==========
os.putenv('SDL_VIDEODRIVER', 'fbcon'                 )
os.putenv('SDL_FBDEV'      , '/dev/fb0'              )
os.putenv('SDL_MOUSEDRV'   , 'TSLIB'                 )
os.putenv('SDL_MOUSEDEV'   , '/dev/input/touchscreen')

# =========-PyGame-==========
print(pg.init())
pg.mouse.set_visible(False)
screen = pg.display.set_mode(size)
mid_font = pg.font.Font(None, 50)
small_font = pg.font.Font(None, 30)

# ========-Text-========
main_text = Button(mid_font, 'Welcome!', (160,50), WHITE)
curr_pos = Button(small_font, "{0:.4f}, {1:.4f}", (160,80), WHITE, defaultfmt=(0,0))
quit_btn = Button(small_font, "Close", (w/2,h-30), WHITE)

yes_btn = Button(small_font, "Yes", (50,h-30), WHITE)
no_btn = Button(small_font, 'No', (w-50,h-30), WHITE)

item_text = Button(mid_font, 'Your item is at:', (160, 50), WHITE)
item_pos  = Button(mid_font, '{0:.4f}, {1:.4f}', (160, 100), WHITE, defaultfmt=(0,0))
found_btn = Button(small_font, 'Found', (w/2, h-30), WHITE)

# ========-Images-========
#pointer  = Image("Resources/pointer.png", (50,50), [0,0], (w/2 - 50, h/2 - 50))

# ============-Objects-==============
uart = serial.Serial("/dev/ttyS0", baudrate=9600, timeout=3000)
gps = adafruit_gps.GPS(uart)
rfid = RFID(2, 10)
clk = pg.time.Clock()

# ==========-Variables-============
gps_hist = [(None, None)]
mousecoords = 0,0
quit = False
yes = False
no = False
found = False

# =========-FSM-==========
STATES = [
  'Initial', # default state
  'Welcome',
  'Question',
  'Finding'
]
STATE = 'Initial'

# ========-Game Loop-========
try:
    while gpio.input(qpin) and not quit:
        # First step of each loop is to read events, do updates, and tick the clock
        clk.tick(framerate)
        yes= False
        no= False
        found= False

        gpsevent = False
        if gps.update():
            gpsevent = True

        rfidevent = False
        if rfid.update():
            rfidevent = True

        mouseevent = False
        for event in pg.event.get():
            if event.type is MOUSEBUTTONUP:
                mousecoords = pg.mouse.get_pos()
                mouseevent = True

        # Mouse event state interactions
        if mouseevent:
            if  STATE == 'Initial' or STATE == 'Welcome':
                if quit_btn.pressed(mousecoords):
                    quit=True
            elif STATE == 'Question':
                if yes_btn.pressed(mousecoords):
                    yes=True
                if no_btn.pressed(mousecoords):
                    no=True
            elif STATE == 'Finding':
                if found_btn.pressed(mousecoords):
                    found=True

        # GPS event state interactions
        if gpsevent:
            gps_hist.append((gps.latitude, gps.longitude))

        # State transitions:
        # Default is 'Initial'
        # Transfer to 'Initial' if gps loses lock
        if STATE not in STATES or (gps_hist[-1][0] is None):
            STATE = 'Initial'
        # Continually clear GPS history until we get a lock
        # Transition to Welcome once we get a lock
        if STATE == 'Initial':
            if gpsevent:
                if gps_hist[-1][0] is not None:
                    STATE = 'Welcome'
                else:
                    gps_hist.pop()
        # Transition to 'Question' from 'Initial' if missed period with tag
        # On loopback transition, clear the GPS coordinates history
        elif STATE == 'Welcome':
            if rfidevent:
                if not rfid.isItemPresent():
                    STATE = 'Question'
                else:
                    coords = gps_hist[-1]
                    gps_hist.clear()
                    gps_hist.append(coords)
        # Transition to 'Finding' from 'Question' if Button Yes
        # Transition to 'Welcome' from 'Question' if Button No
        elif STATE == 'Question':
            if rfidevent and rfid.isItemPresent():
                STATE = 'Welcome'
            elif yes:
                STATE = 'Finding'
            elif no:
                STATE = 'Welcome'
        # Transition to 'Welcome' from 'Finding' if Button Found or new period with tag
        elif STATE == 'Finding':
            if found or (rfidevent and rfid.isItemPresent()):
                STATE = 'Welcome'

        # Rendering cell
        screen.fill(DARKGREY)

        if STATE == 'Initial':
            main_text.set_text('Waiting for GPS!')
            screen.blit(*main_text.render())
            screen.blit(*quit_btn.render())

        if STATE == 'Welcome':
            main_text.set_text('Welcome!')
            screen.blit(*main_text.render())

            curr_pos.fmt(gps_hist[-1])
            screen.blit(*curr_pos.render())

            screen.blit(*quit_btn.render())

        if STATE == 'Question':
            main_text.set_text('Is your item lost?')
            screen.blit(*main_text.render())

            screen.blit(*yes_btn.render())
            screen.blit(*no_btn.render())

        if STATE == 'Finding':
            item_pos.fmt(gps_hist[0])
            screen.blit(*item_text.render())
            screen.blit(*item_pos.render())
            
            # If/when we figure out pathing:
            # pointer.rotate(10*(mousecoords[0]-mousecoords[1])/(1+mousecoords[0]+mousecoords[1]), absolute=False)
            # screen.blit(*pointer.render())

            screen.blit(*found_btn.render())

        pg.display.flip()
except KeyboardInterrupt:
    gpio.cleanup()
    pg.quit()

gpio.cleanup()
pg.quit()